package com.zlcx.tz.live.service.task;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.zlcx.tz.live.constant.RedisKeyConst;
import com.zlcx.tz.live.domain.LiveRoomInfo;
import com.zlcx.tz.live.domain.LiveRoomJoinRecord;
import com.zlcx.tz.live.domain.LiveRoomPublisherRecord;
import com.zlcx.tz.live.enums.RoomJoinStatus;
import com.zlcx.tz.live.enums.RoomState;
import com.zlcx.tz.live.enums.RoomUserRole;
import com.zlcx.tz.live.exception.SendExceptionUtil;
import com.zlcx.tz.live.service.impl.*;
import com.zlcx.tz.live.utils.CalendarUtil;
import com.zlcx.tz.live.utils.SimpleLockUtil;
import com.zlcx.tz.live.vo.RoomInfoVO;
import com.zlcx.tz.live.vo.UserJoinVO;
import com.zlcx.tz.live.vo.UserVO;
import com.zlcx.tz.live.vo.page.PageResultVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p></p>
 * <PRE>
 * <BR>    修改记录
 * <BR>-----------------------------------------------
 * <BR>    修改日期         修改人          修改内容
 * </PRE>
 *
 * @author zl
 * @version 1.0
 * @date Created in 2019/4/23 17:21
 * @copyright: Copyright (c) founders
 */
@Service
@Slf4j
@EnableScheduling
public class TaskService {

    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    LiveRoomService roomService;
    @Autowired
    LiveRoomJoinRecordService roomJoinRecordService;
    @Autowired
    LiveRoomPublisherService publisherService;
    @Resource(name = "taskExecutor")
    AsyncTaskExecutor asyncTaskExecutor;
    @Autowired
    MessageService messageService;
    @Autowired
    UserService userService;
    @Autowired
    SimpleLockUtil simpleLockUtil;
    @Autowired
    AgoraService agoraService;
    @Value("${eureka.debug:false}")
    private Boolean debug;
    @Value("${sunlands.liveTimeOut}")
    private Integer liveTimeOut;
    @Value("${agora.alertTime}")
    private String alertTime;
    @Autowired
    private UploadService uploadService;
    @Autowired
    private LiveRoomService liveRoomService;
    @Autowired
    SendExceptionUtil emailService;

    private Logger traceLoger = LoggerFactory.getLogger("traceloger");

    @Scheduled(cron = "${scheduled.checkLiveTask}")
    public void checkLiveTask() {

        if (debug) {
            log.warn("本地环境不启动定时");
            return;
        }

        Date now = new Date();
        if (!getCheckLiveTaskLock()) {
            return;
        }

        try {
            Integer page = 0, pageSize = 500;
            boolean hasNextpage = true;
            while (hasNextpage) {
                PageResultVO pageResultVO = roomService.findRoomList(null, page, pageSize);
                log.debug("[checkLiveTask] page={},pageSize={},pageResultVO={}", page, pageSize, JSON.toJSONString(pageResultVO));
                page += 1;
                List<RoomInfoVO> roomInfoVOS = pageResultVO.getRows();
                if (CollectionUtils.isEmpty(roomInfoVOS)) {
                    break;
                }
                if (pageResultVO.getRows().size() < pageSize) {
                    hasNextpage = false;
                }

                for (RoomInfoVO roomInfoVO : roomInfoVOS) {

                    if (roomInfoVO.getCreateTime().after(DateUtils.addSeconds(now, -60))) {
                        continue;
                    }

                    LiveRoomInfo roomInfo = new LiveRoomInfo();
                    BeanUtils.copyProperties(roomInfoVO, roomInfo);

                    LiveRoomJoinRecord hostUserJoinRecord = roomJoinRecordService.getHostUserJoinRecord(roomInfoVO.getId());

                    List<UserJoinVO> joinUserList = roomService.findUserListByRoomId(roomInfoVO.getId(), Lists.newArrayList(RoomUserRole.OWNER.getCode(), RoomUserRole.PUBLISHER.getCode()));
                    if (CollectionUtils.isEmpty(joinUserList)) {
                        traceLoger.info("[TaskService] 无直播角色，系统关闭房间，roomId={}", roomInfoVO.getId());
                        log.info("[TaskService] 无直播角色，系统关闭房间，roomId={}", roomInfoVO.getId());
                        try {
                            roomService.updateRoomState(roomInfoVO.getId(), RoomState.CLOSED.getCode());
                            roomJoinRecordService.closeRoomUpdateJoinRecordState(roomInfoVO.getId(), RoomState.CLOSED.getCode());
                            publisherService.closeRoomQuitPublish(roomInfoVO.getId());
                            messageService.closeRoom(roomInfo, hostUserJoinRecord.getUserId(), RoomState.CLOSED.getCode());
//                            agoraService.createKickingRule(roomInfoVO.getId(),null,null,60);
                        } catch (Exception e) {
                            log.error("[TaskService] 无直播角色，系统关闭房间异常，roomId={}，e={}", roomInfoVO.getId(), e);
                        }
                        continue;
                    }

                    List<LiveRoomPublisherRecord> publisherRecordList = publisherService.getPublishingList(roomInfoVO.getId());
                    if (CollectionUtils.isEmpty(publisherRecordList)) {
                        traceLoger.info("[TaskService] 无人直播，系统关闭房间，roomId={}", roomInfoVO.getId());
                        log.info("[TaskService] 无人直播，系统关闭房间，roomId={}", roomInfoVO.getId());
                        //没人直播，让在房间中的人都退出，关闭房间
                        try {
                            roomService.updateRoomState(roomInfoVO.getId(), RoomState.CLOSED.getCode());
                            roomJoinRecordService.closeRoomUpdateJoinRecordState(roomInfoVO.getId(), RoomState.CLOSED.getCode());
                            messageService.closeRoom(roomInfo, hostUserJoinRecord.getUserId(), RoomState.HEART_MISS_CLOSED.getCode());
//                            agoraService.createKickingRule(roomInfoVO.getId(),null,null,60);
                        } catch (Exception e) {
                            log.error("[TaskService] 无人直播，系统关闭房间异常，roomId={}，e={}", roomInfoVO.getId(), e);
                        }
                        continue;
                    }

                    for (int i = 0; i < publisherRecordList.size(); i++) {
                        LiveRoomPublisherRecord publisherRecord = publisherRecordList.get(i);
                        try {

                            String liveTaskKey = RedisKeyConst.getLiveTaskKey(publisherRecord.getUserId(), roomInfoVO.getId());
                            Date liveDate = (Date) redisTemplate.opsForValue().get(liveTaskKey);
                            String liveDateStr = "";
                            if (liveDate != null) {
                                liveDateStr = DateFormatUtils.ISO_DATETIME_FORMAT.format(liveDate);
                            }
                            if (liveDate != null && now.before(DateUtils.addSeconds(liveDate, liveTimeOut))) {
                                continue;
                            }

                            Long lostLiveCount = 0L;
                            String lostLiveCountKey = RedisKeyConst.getLostLiveCountKey(publisherRecord.getUserId(), roomInfoVO.getId());
                            Object n = redisTemplate.opsForValue().get(lostLiveCountKey);
                            if (n != null) {
                                lostLiveCount = Long.parseLong(n.toString());
                            }
                            if (lostLiveCount < 2) {
                                lostLiveCount++;
                                redisTemplate.opsForValue().set(lostLiveCountKey, lostLiveCount, 1, TimeUnit.HOURS);
                                continue;
                            }

//                        AgoraChannelUserStateVO userStateVO = agoraService.getChanelUserState(roomInfoVO.getId(),publisherRecord.getUserId());
//                        log.info("[TaskService] 主播断线声网状态，roomId={}，userId={},userStateVO={}",roomInfoVO.getId(),publisherRecord.getUserId(), JSON.toJSONString(userStateVO));
                            traceLoger.info("[TaskService] 主播断线系统踢出房间，roomId={}，userId={},liveDateStr={},now={}", roomInfoVO.getId(), publisherRecord.getUserId(), liveDateStr, DateFormatUtils.ISO_DATETIME_FORMAT.format(now));
                            log.info("[TaskService] 主播断线系统踢出房间，roomId={}，userId={},liveDateStr={},now={}", roomInfoVO.getId(), publisherRecord.getUserId(), liveDateStr, DateFormatUtils.ISO_DATETIME_FORMAT.format(now));
                            UserVO userVO = userService.getUserInfo(publisherRecord.getUserId());
                            roomJoinRecordService.exitRoom(roomInfoVO.getId(), publisherRecord.getUserId(), RoomJoinStatus.OUT.getCode(), RoomState.HEART_MISS_CLOSED.getCode());
                            messageService.exitRoom(userVO, roomInfo, false);
//                            messageService.notNonalExit(userVO, roomInfo);
//                            Long id = agoraService.createKickingRule(roomInfoVO.getId(),publisherRecord.getUserId(),null,1);
//                            agoraService.deleteKickingRule(id);
                        } catch (Exception e) {
                            log.error("[TaskService] 主播断线系统踢出房间异常，roomId={}，userId={},e={}", roomInfoVO.getId(), publisherRecord.getUserId(), e);
                        }

                    }

                }

            }

            List<LiveRoomPublisherRecord> noCancelTimeoutRecordList = publisherService.getNoCancelTimeoutRecord();
            if (CollectionUtils.isNotEmpty(noCancelTimeoutRecordList)) {
                for (LiveRoomPublisherRecord publisherRecord : noCancelTimeoutRecordList) {
                    traceLoger.info("[TaskService] 申请超时系统取消申请，roomId={}，userId={}", publisherRecord.getRoomId(), publisherRecord.getUserId());
                    log.info("[TaskService] 申请超时系统取消申请，roomId={}，userId={}", publisherRecord.getRoomId(), publisherRecord.getUserId());
                    try {
                        publisherService.cancelApplyPublish(publisherRecord.getRoomId(), publisherRecord.getUserId());
                    } catch (Exception e) {
                        log.info("[TaskService] 申请超时系统取消申请操作异常，roomId={}，userId={},e={}", publisherRecord.getRoomId(), publisherRecord.getUserId(), e);
                    }
                }
            }

            List<LiveRoomJoinRecord> notNonalQuitUserList = roomJoinRecordService.getNotNonalQuitUser();
            if (CollectionUtils.isNotEmpty(notNonalQuitUserList)) {
                for (LiveRoomJoinRecord roomJoinRecord : notNonalQuitUserList) {
                    traceLoger.info("[TaskService] 异常退出系统踢出房间，roomId={}，userId={}", roomJoinRecord.getRoomId(), roomJoinRecord.getUserId());
                    log.info("[TaskService] 异常退出系统踢出房间，roomId={}，userId={}", roomJoinRecord.getRoomId(), roomJoinRecord.getUserId());
                    try {
                        roomJoinRecordService.exitRoom(roomJoinRecord.getRoomId(), roomJoinRecord.getUserId(), RoomJoinStatus.OUT.getCode(), RoomState.CLOSED.getCode());
                    } catch (Exception e) {
                        log.error("[TaskService] 异常退出系统踢出房间操作异常，roomId={}，userId={}，e={}", roomJoinRecord.getRoomId(), roomJoinRecord.getUserId(), e);
                    }
                }
            }

            List<LiveRoomPublisherRecord> notNonalQuitPublisherList = publisherService.getNotNonalQuitPublisherList();
            if (CollectionUtils.isNotEmpty(notNonalQuitPublisherList)) {
                for (LiveRoomPublisherRecord publisherRecord : notNonalQuitPublisherList) {
                    traceLoger.info("[TaskService] 主播异常退出系统踢出房间，roomId={}，userId={}", publisherRecord.getRoomId(), publisherRecord.getUserId());
                    log.info("[TaskService] 主播异常退出系统踢出房间，roomId={}，userId={}", publisherRecord.getRoomId(), publisherRecord.getUserId());
                    try {
                        publisherService.finishPublish(publisherRecord.getRoomId(), publisherRecord.getUserId(), RoomState.CLOSED.getCode());
                    } catch (Exception e) {
                        log.error("[TaskService] 主播异常退出系统踢出房间操作异常，roomId={}，userId={},e={}", publisherRecord.getRoomId(), publisherRecord.getUserId(), e);
                    }
                }
            }
            roomService.closeUnnormalClosedRoom();
        } catch (Exception e) {
            log.error("doCheckLiveTask error,e={}", e);
        } finally {
            releaseCheckLiveTaskLock();
        }

    }

    //    @Scheduled(cron = "${scheduled.alertAcountMounthUsage}")
    public void alertAcountMounthUsage() {
        if (debug) {
            log.warn("本地环境不启动定时");
            return;
        }

        if (!getAlertAcountMounthUsageLockKey()) {
            return;
        }
        try {
            agoraService.alertAcountMounthUsage();

            try {
                String dayPath = CalendarUtil.getFormatDateToString(CalendarUtil.addOrBeforeNDay(new Date(), -4));
                uploadService.deleteFilesByDayPath(dayPath);
                dayPath = CalendarUtil.getFormatDateToString(CalendarUtil.addOrBeforeNDay(new Date(), -3));
                uploadService.deleteFilesByDayPath(dayPath);
            } catch (Exception e) {
                log.error("[TaskService] 定时删除图片失败！");
            }

        } catch (Exception e) {
            log.error("[TaskService] alertAcountMounthUsage error,e={}", e);
        } finally {
            releaseAlertAcountMounthUsageLockKey();
        }
    }


    @Scheduled(cron = "${scheduled.autoCloseRoom}")
    public void autoCloseRoom() {
        if (debug) {
            log.warn("本地环境不启动定时");
            return;
        }

        if (!getAutoCloseRoomLockKey()) {
            return;
        }

        log.info("8小时自动关闭的连麦房间信息数据");
        try {
            int i = 0;
            boolean flag = true;
            Pageable pageable = new PageRequest(0, 100);
            Page<LiveRoomInfo> page = null;
            while (flag || page.hasNext()) {
                flag = false;
                page = liveRoomService.find1HourOpenRoom(pageable);
                List<LiveRoomInfo> openRoomList = page.getContent();
                i += openRoomList.size();
                openRoomList.forEach(room ->
                {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.info("1小时自动关闭房间信息 room = {}", room);
                    asyncTaskExecutor.execute(() -> {
                        LiveRoomJoinRecord joinRecord = roomJoinRecordService.getHostUserJoinRecord(room.getId());
                        if (Objects.isNull(joinRecord)) {
                            log.error("1小时自动关闭，没有查询到房主信息 room = {} 强制重置房间状态", room);
                            liveRoomService.updateRoomState(room.getId(), RoomState.BE_CLOSED.getCode());
                            return;
                        }
                        try {
                            liveRoomService.autoClosedRoom(joinRecord.getUserId(), room.getId());
                        } catch (Exception e) {
                            log.error("", e);
                        }
                    });
                });
            }
            log.info("1小时自动关闭房间, 共关闭房间信息 {} 条", i);
        } catch (Exception e) {
            emailService.sendException("1小时自动关闭房间出错 任务已停止 错误信息 = " + e.getMessage(), e);
            log.error("1小时自动关闭房间出错！");
            log.error("", e);
        } finally {
            releaseAutoCloseRoomLockKey();
        }
    }


    /**
     * 由定时任务微服务触发
     * 备份所有已经关闭的房间数据
     */
    @Scheduled(cron = "${scheduled.backupCloseRoom}")
    public void backupCloseRoom() {
        if (debug) {
            log.warn("本地环境不启动定时");
            return;
        }

        if (!backupCloseRoomLockKey()) {
            return;
        }


        log.info("开始备份已经关闭的连麦房间信息数据");
        try {
            int i = 0;
            boolean flag = true;
            Pageable pageable = new PageRequest(0, 100);
            Page<LiveRoomInfo> page = null;
            while (flag || page.hasNext()) {
                flag = false;
                i++;
                page = liveRoomService.findAllClosedRoom(pageable);
                List<LiveRoomInfo> closeRoomList = page.getContent();
                closeRoomList.forEach(room ->
                {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.info("备份房间信息 room = {}", room);
                    asyncTaskExecutor.execute(() ->
                            liveRoomService.backup(room));

                });
            }
            log.info("备份房间信息结束, 共备份信息 {} 条", i * 100);
        } catch (Exception e) {
            emailService.sendException("备份关闭房间信息出错 任务已停止 错误信息 = " + e.getMessage(), e);
            log.error("备份关闭房间信息出错！");
            log.error("", e);
        } finally {
            releaseBackupCloseRoomLockKey();
        }

    }


    private void releaseCheckLiveTaskLock() {
        String lockKey = RedisKeyConst.getCheckLiveTaskLockKey();
        try {
            simpleLockUtil.releaseLock(lockKey, this.getClass().getName());
        } catch (Exception e) {
            log.error("releaseCheckLiveTaskLock error", e);
        }
    }

    private boolean getCheckLiveTaskLock() {
        String lockKey = RedisKeyConst.getCheckLiveTaskLockKey();

        try {
            return simpleLockUtil.acquireLock(lockKey, this.getClass().getName(), 30000);
        } catch (Exception e) {
            log.error("getCheckLiveTaskLock error", e);
        }
        return false;
    }

    private void releaseAlertAcountMounthUsageLockKey() {
        String lockKey = RedisKeyConst.getAlertAcountMounthUsageLockKey();
        try {
            simpleLockUtil.releaseLock(lockKey, this.getClass().getName());
        } catch (Exception e) {
            log.error("releaseAlertAcountMounthUsageLockKey error", e);
        }
    }

    private void releaseAutoCloseRoomLockKey() {
        String lockKey = RedisKeyConst.getAutoCloseRoomLockKey();
        try {
            simpleLockUtil.releaseLock(lockKey, this.getClass().getName());
        } catch (Exception e) {
            log.error("releaseAutoCloseRoomLockKey error", e);
        }
    }

    private void releaseBackupCloseRoomLockKey() {
        String lockKey = RedisKeyConst.getBackupCloseRoomLockKey();
        try {
            simpleLockUtil.releaseLock(lockKey, this.getClass().getName());
        } catch (Exception e) {
            log.error("releaseBackupCloseRoomLockKey error", e);
        }
    }

    private boolean getAlertAcountMounthUsageLockKey() {
        String lockKey = RedisKeyConst.getAlertAcountMounthUsageLockKey();

        try {
            return simpleLockUtil.acquireLock(lockKey, this.getClass().getName(), 30000);
        } catch (Exception e) {
            log.error("", e);
        }
        return false;
    }


    private boolean getAutoCloseRoomLockKey() {
        String lockKey = RedisKeyConst.getAutoCloseRoomLockKey();

        try {
            return simpleLockUtil.acquireLock(lockKey, this.getClass().getName(), 300000);
        } catch (Exception e) {
            log.error("", e);
        }
        return false;
    }

    private boolean backupCloseRoomLockKey() {
        String lockKey = RedisKeyConst.getBackupCloseRoomLockKey();

        try {
            return simpleLockUtil.acquireLock(lockKey, this.getClass().getName(), 30000);
        } catch (Exception e) {
            log.error("", e);
        }
        return false;
    }


}
